/*
 *  Copyright 2008 The MITRE Corporation (http://www.mitre.org/). All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.mitre.lattice.lattice;

import java.io.Serializable;
import java.util.ArrayList;

import org.mitre.mrald.util.Config;
/**
 *  This LatticeNode class acts as a node on the LatticeTree structure. Each of
 *  these LatticeNodes on the LatticeTree identifies a Community of Interest
 *  (COI) and eventually this will be tied to the label of images and users in
 *  the MITRE Lattice Security package.
 *
 *@author     Todd Cornett, tcornett@mitre.org, The MITRE Corporation
 *@created    June 5, 2003
 *@see        LatticeTree
 *@version    1.0
 */
public class LatticeNode extends Object implements LatticeNodeInterface, Serializable, Comparable {
	/**
	 *  The unique identifier for the Lattice Node
	 */
	private String name = null;

	/*
	 *  The children and parent arrays are the list of the
	 *  associated parents and children with that particular node
	 */
	private ArrayList<LatticeNode> children = new ArrayList<LatticeNode>();
	private ArrayList<LatticeNode> parents = new ArrayList<LatticeNode>();


	/**
	 *  Constructs a new LatticeNode instance with the name, children, and parents
	 *  set to null
	 */
	public LatticeNode() {
		this.name = "";
		this.children = new ArrayList<LatticeNode>();
		this.parents = new ArrayList<LatticeNode>();
	}


	/**
	 *  Constructs a new LatticeNode instance with the name, equal to the specific
	 *  name and the children and parents set to null
	 *
	 *@param  nodeName  The name to be assigned to this LatticeNode
	 */
	public LatticeNode(String nodeName) {

		//nodeName = MiscUtils.checkApostrophe(nodeName);
		this.name = nodeName;
		//this.children = new ArrayList();
		//this.parents = new ArrayList();

	}


	/**
	 *  Constructs a new LatticeNode instance with the name, children and parents
	 *  set to the specified values
	 *
	 *@param  nodeName  The name to be assigned to this LatticeNode
	 *@param  children  The ArrayList of LatticeNodes that will be assigned to this
	 *      LatticeNode's children
	 *@param  parents   The ArrayList of LatticeNodes that will be assigned to this
	 *      LatticeNode's parents
	 */
	public LatticeNode(String nodeName, ArrayList<LatticeNode> children, ArrayList<LatticeNode> parents) {
		this.name = nodeName;
		this.children = children;
		this.parents = parents;
	}


	/**
	 *  Constructs a new LatticeNode instance with the name, children and parents
	 *  set to the values of the specified LatticeNode argument.
	 *
	 *@param  newNode  The LatticeNode that will be used as the source of the
	 *      values for the new LatticeNode
	 */
	public LatticeNode(LatticeNode newNode) {

		this.name = new String(newNode.getName());
		this.children = new ArrayList<LatticeNode>(newNode.getChildren());
		this.parents = new ArrayList<LatticeNode>(newNode.getParents());
	}


	/**
	 *  Returns the children attribute of the LatticeNode object
	 *
	 *@return    The children value for this LatticeNode
	 */
	public ArrayList<LatticeNode> getChildren() {
		return this.children;
	}


	/**
	 *  Assigns the specified ArrayList of LatticeNodes to the children object of
	 *  the LatticeNode
	 *
	 *@param  children                              The ArrayList of LatticeNodes
	 *      that will be assigned to this LatticeNode's children
	 *@return                                       true if the list is added
	 *      completely; false otherwise
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public boolean setChildren(ArrayList children) throws InvalidLatticeStructureException {
		return addChildren(children);
	}


	/**
	 *  Returns the identifier, or name attribute, of the LatticeNode object
	 *
	 *@return    The identifier, or name, as a String
	 */
	public String getName() {
		return name;
	}


	/**
	 *  Assigns the specified String to the identifier, or name, of the LatticeNode
	 *
	 *@param  name  The name to be assigned to this LatticeNode
	 */
	public void setName(String name) {
		this.name = name;
	}


	/**
	 *  Returns the parents attribute of the LatticeNode object
	 *
	 *@return    The parents value for this LatticeNode
	 */
	public ArrayList<LatticeNode> getParents() {
		return this.parents;
	}


	/**
	 *  Returns the parents attribute of the LatticeNode object
	 *
	 *@return    The parents value for this LatticeNode
	 */
	public ArrayList getParentsClone() {
		ArrayList<LatticeNode> newParents = new ArrayList<LatticeNode>();
		for (int i = 0; i < parents.size(); i++) {
			newParents.add(Config.getLatticeFactory().copyNode(parents.get(i)));
		}
		return newParents;
	}


	/**
	 *  Assigns the specified ArrayList of LatticeNodes to the parents object of
	 *  the LatticeNode
	 *
	 *@param  parents                               The ArrayList of LatticeNodes
	 *      that will be assigned to this LatticeNode's parents
	 *@return                                       true if the list is added
	 *      completely; false otherwise
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public boolean setParents(ArrayList parents) throws InvalidLatticeStructureException {
		return addParents(parents);
	}


	/**
	 *  Assigns the specified ArrayList of LatticeNodes to the children object of
	 *  the LatticeNode
	 *
	 *@param  nodeChildren                          The feature to be added to the
	 *      Children attribute
	 *@return                                       true if the list is added
	 *      completely; false otherwise
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public boolean addChildren(ArrayList nodeChildren) throws InvalidLatticeStructureException {

		for (int i = 0; i < nodeChildren.size(); i++) {
			if (!addChild((LatticeNode) nodeChildren.get(i))) {
				return false;
			}
		}
		return true;
	}


	/**
	 *  Removes the specified ArrayList of LatticeNodes from the LatticeNode's
	 *  children
	 *
	 *@param  nodeChildren  Description of the Parameter
	 *@return               true if the list is removed completely; false otherwise
	 */
	public boolean removeChildren(ArrayList nodeChildren) {
		for (int i = 0; i < nodeChildren.size(); i++) {
			if (!removeChild((LatticeNode) nodeChildren.get(i))) {
				return false;
			}
		}
		return true;
	}


	/**
	 *  Adds individual LatticeNode to the children list of this LatticeNode
	 *
	 *@param  child                                 The specified child to be added
	 *      to the children of this LatticeNode
	 *@return                                       true if the child is removed
	 *      successfully; false otherwise
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public boolean addChild(LatticeNode child) throws InvalidLatticeStructureException {
		ArrayList<LatticeNode> checkNodes = new ArrayList<LatticeNode>();
		if (!checkIsActionValid( false, child))
	        {
			throw new InvalidLatticeStructureException("LATT-002", "This action is Invalid.");
		}
		checkNodes.add(child);
		if (!checkConsistent(false, checkNodes)) {
			throw new InvalidLatticeStructureException("LATT-002", "The node " + child.getName() + " is already a Parent group of " + this.getName() + " \n and cannot therefore, also be a Child");
		}

		if (this.children.contains(child)) {
			return false;
		} else {
			return this.children.add(child);
		}
	}


	/**
	 *  Removes individual LatticeNode from the children list of this LatticeNode
	 *
	 *@param  child  The specified child to be removed from the children of this
	 *      LatticeNode
	 *@return        true if the child is removed successfully; false otherwise
	 */
	public boolean removeChild(LatticeNode child) {
		if (!this.children.contains(child)) {
			return false;
		} else {
			this.children.remove(children.indexOf(child));
			return true;
		}
	}


	/**
	 *  Assigns the specified ArrayList of LatticeNodes to the parents object of
	 *  the LatticeNode
	 *
	 *@param  nodeParents                           The feature to be added to the
	 *      Parents attribute
	 *@return                                       true if the list is added
	 *      completely; false otherwise
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public boolean addParents(ArrayList nodeParents) throws InvalidLatticeStructureException {
		for (int i = 0; i < nodeParents.size(); i++) {
			if (!addParent((LatticeNode) nodeParents.get(i))) {
				return false;
			}
		}
		return true;
	}

       /**
	 *
	 *
	 *@param   boolean	isParent, whether this Node is a parent or child type
	 *         LatticeNode	The Node being added
	 *@return  true if this Action is allowed; false otherwise.
	 */
	public boolean checkIsActionValid(boolean isParent, LatticeNode addNode)
	{

		return true;
	}

	/**
	 *  CHecks that the array being passed in is still consistent with either the
	 *  parent or child arrays
	 *
	 *@param  checkNodes     Description of the Parameter
	 *@param  checkChildren  Description of the Parameter
	 *@return                true if the list is removed completely; false
	 *      otherwise
	 */
	public boolean checkConsistent(boolean checkChildren, ArrayList checkNodes) {
		boolean test = true;

		//Check to see if the parent being added is contained
		//within the Child nodes.
		if (checkChildren) {
			for (int i = 0; i < checkNodes.size(); i++) {
				if (!children.contains(checkNodes.get(i))) {
					test = true;

					for (int j = 0; j < children.size(); j++) {
						LatticeNode child = children.get(j);
						test = child.checkConsistent(checkChildren, checkNodes);
					}
					//If inconsistency detected
					if (!test) {
						return test;
					}
				} else {
					test = false;
					return test;
				}
			}
			return true;
		} else {
			for (int i = 0; i < checkNodes.size(); i++) {
				if (!parents.contains(checkNodes.get(i))) {
					test = true;

					for (int j = 0; j < parents.size(); j++) {
						LatticeNode parent = parents.get(j);
						test = parent.checkConsistent(checkChildren, checkNodes);
					}
					//If inconsistency detected
					if (!test) {
						return test;
					}
				} else {
					test = false;
					return test;
				}
			}
			return true;
		}

	}


	/**
	 *  Remove the child and reassign the permissions to be the children of this
	 *  node.
	 *
	 *@param  child                                 Description of the Parameter
	 *@return                                       true if all the grandchild
	 *      objects were successfully reassigned false if there are no grandchild
	 *      objects. Which means that the child node is the root node.
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public boolean reAssignChild(LatticeNode child) throws InvalidLatticeStructureException {
		//Bump down the permissions.
		//Connect the parent to each of the children of the
		//node that link was just removed from.

		children.remove(child);

		ArrayList grandChildren = child.getChildren();

		if (grandChildren.size() == 0) {
			return false;
		}

		for (int j = 0; j < grandChildren.size(); j++) {
			LatticeNode grandChild = (LatticeNode) grandChildren.get(j);

			//If the grandchild is Public, and the node has more than one child
			//(i.e. an indirect link to Public already exists Then do not create link to Public.

			if ((this.children.size() > 0) && (grandChild.getName().equals("Public"))) {
				continue;
			}

			addChild(grandChild);
			grandChild.addParent(this);
		}
		return true;
	}


	/**
	 *  If the reassign through the child cannot work, in the case where the link
	 *  being deleted is a Public node, then need to reassign via the parent
	 *  objects
	 *
	 *@param  rootNode                              Description of the Parameter
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public void reAssignParentsToPublic(LatticeNode rootNode) throws InvalidLatticeStructureException {
		//Get all the parent objects
		//Reassign to the Root Node.
		for (int j = 0; j < parents.size(); j++) {
			LatticeNode parent = parents.get(j);
			parent.addChild(rootNode);
			rootNode.addParent(parent);
		}

	}


	/**
	 *  Removes the specified ArrayList of LatticeNodes from the LatticeNode's
	 *  parents
	 *
	 *@param  nodeParents  Description of the Parameter
	 *@return              true if the list is removed completely; false otherwise
	 */
	public boolean removeParents(ArrayList nodeParents) {
		for (int i = 0; i < nodeParents.size(); i++) {
			if (!removeParent((LatticeNode) nodeParents.get(i))) {
				return false;
			}
		}
		return true;
	}


	/**
	 *  Adds individual LatticeNode to the parents list of this LatticeNode
	 *
	 *@param  parent                                The specified parent to be
	 *      added to the parents of this LatticeNode
	 *@return                                       true if the parent is removed
	 *      successfully; false otherwise
	 *@exception  InvalidLatticeStructureException  Description of the Exception
	 */
	public boolean addParent(LatticeNode parent) throws InvalidLatticeStructureException {
		ArrayList<LatticeNode> checkNodes = new ArrayList<LatticeNode>();
		if (!checkIsActionValid( true, parent))
	        {
			throw new InvalidLatticeStructureException("LATT-002", "This action is Invalid.");
		}

		checkNodes.add(parent);
		if (!checkConsistent(true, checkNodes)) {
			throw new InvalidLatticeStructureException("LATT-002", "The node " + parent.getName() + " is already a Child group of " + this.getName() + " \n and cannot therefore, also be a Parent");
		}

		if (this.parents.contains(parent)) {
			return false;
		} else {
			return this.parents.add(parent);
		}
	}


	/**
	 *  Removes individual LatticeNode from the parent list of this LatticeNode
	 *
	 *@param  parent  The specified parent to be added to the parents of this
	 *      LatticeNode
	 *@return         true if the parent is removed successfully; false otherwise
	 */
	public boolean removeParent(LatticeNode parent) {
		if (!this.parents.contains(parent)) {
			return false;
		} else {
			this.parents.remove(parents.indexOf(parent));
			return true;
		}
	}


	/**
	 *  Determines equality by comparing the identifier, or name, of the current
	 *  and specified LatticeNodes
	 *
	 *@param  currentNode  The LatticeNode being compared to the current node
	 *@return              true is the identifiers, or names, are found to be
	 *      equal; false otherwise
	 */
	public boolean equals(Object currentNode) {

		boolean result = false;

		if (this.getName().equals(((LatticeNode) currentNode).getName())) {
			result = true;
		} else if (this.getName() == ((LatticeNode) currentNode).getName()) {
			result = true;
		} else if (this == currentNode) {
			result = true;
		} else {
			result = false;
		}

		return result;
	}


	/**
	 *  Adds individual LatticeNode to the parents list of this LatticeNode
	 *
	 *@return    true if the parent is removed successfully; false otherwise
	 */
	public int countParentNodes() {
		return parents.size();
	}


	/**
	 *  Adds individual LatticeNode to the parents list of this LatticeNode
	 *
	 *@return    true if the parent is removed successfully; false otherwise
	 */
	public int countChildNodes() {
		return children.size();
	}


	/**
	 *  Returns a hash code value for the object. This method is supported for the
	 *  benefit of hashtables such as those provided by java.util.Hashtable
	 *
	 *@return    a hash code value for this LatticeNode
	 *@see       LatticeNode.equals( LatticeNode ), HashTable
	 */
	public int hashCode() {
		int hash = 7;
		hash = 31 * hash + (name == null ? 0 : name.hashCode());
		return hash;
	}


	/**
	 *  Returns a hash code value for the object. This method is supported for the
	 *  benefit of hashtables such as those provided by java.util.Hashtable
	 *
	 *@param  comparableLattice  Description of the Parameter
	 *@return                    a hash code value for this LatticeNode
	 *@see                       LatticeNode.equals( LatticeNode ), HashTable
	 */
	public int compareTo(Object comparableLattice) {
		LatticeNode compare = (LatticeNode) comparableLattice;
		return this.getName().compareTo(compare.getName());
	}
}

